/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007-2008 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.security.ws.server;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.phase.Phase;
import org.apache.cxf.phase.PhaseInterceptor;
import org.apache.ws.security.WSConstants;
import org.apache.ws.security.WSSecurityEngineResult;
import org.apache.ws.security.WSUsernameTokenPrincipal;
import org.apache.ws.security.handler.WSHandlerConstants;
import org.apache.ws.security.handler.WSHandlerResult;
import org.springframework.security.Authentication;
import org.springframework.security.AuthenticationManager;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;

/**
 * XCF Handler. Sets a uid/pwd found in a WSS Header in the SimpleSubject.
 */
public class ValidateUserTokenHandler
    implements PhaseInterceptor<SoapMessage>
{

  /**
   * the {@link AuthenticationManager} to use.
   */
  private AuthenticationManager authenticationManager;

  /**
   * verify that the user exist and that the credentials are correct.
   */
  private boolean verifyUser;

  /**
   * the properties of this interceptor.
   */
  private Map<String, Object> properties = new HashMap<String, Object>();

  /**
   * the ids of the interceptors that must be executed before this one.
   */
  private Set<String> before = new HashSet<String>();

  /**
   * the ids of the interceptors that must be executed after this one.
   */
  private Set<String> after = new HashSet<String>();

  /**
   * the phase in which to execute this interceptor.
   */
  private final String phase;

  /**
   * the id of this interceptor.
   */
  private final String id;



  /**
   * constructor.
   */
  public ValidateUserTokenHandler()
  {
    id = getClass().getName();
    phase = Phase.PRE_PROTOCOL;
    getAfter().add(ForgivingWSS4jInInterceptor.class.getName());
  }



  /**
   * getter for the authenticationManager property.
   * 
   * @return the authenticationManager
   */
  public AuthenticationManager getAuthenticationManager()
  {
    return authenticationManager;
  }



  /**
   * setter for the authenticationManager property.
   * 
   * @param authenticationManager
   *          the authenticationManager to set
   */
  public void setAuthenticationManager(AuthenticationManager authenticationManager)
  {
    this.authenticationManager = authenticationManager;
  }



  /**
   * getter for the verifyUser property.
   * 
   * @return the verifyUser
   */
  public boolean isVerifyUser()
  {
    return verifyUser;
  }



  /**
   * setter for the verifyUser property.
   * 
   * @param verifyUser
   *          the verifyUser to set
   */
  public void setVerifyUser(boolean verifyUser)
  {
    this.verifyUser = verifyUser;
  }



  /**
   * /** Called for all interceptors (in reverse order) on which handleMessage
   * had been successfully invoked, when normal execution of the chain was
   * aborted for some reason.
   * 
   * @param message
   *          the message to work with.
   * @see org.apache.cxf.interceptor.Interceptor#handleFault(org.apache.cxf.message.Message)
   */
  public void handleFault(SoapMessage message)
  {
    // Do nothing.
  }



  /**
   * Intercepts a message. Interceptors should NOT invoke handleMessage or
   * handleFault on the next interceptor - the interceptor chain will take care
   * of this.
   * 
   * @param message
   *          the message to work with.
   * @see org.apache.cxf.interceptor.Interceptor#handleMessage(org.apache.cxf.message.Message)
   */
  public void handleMessage(SoapMessage message)
  {
    if (message.get(ForgivingWSS4jInInterceptor.class.getName()) != null) {
      // see ForgivingWSS4jInHandler... if an Exception was caught there but
      // should ignored, stop here
      SecurityContextHolder.clearContext();
      return;
    }
    Vector<?> result = (Vector<?>) message.get(WSHandlerConstants.RECV_RESULTS);
    if (result == null) {
      throw new IllegalArgumentException(WSHandlerConstants.RECV_RESULTS + " Property not found in MessageContext?!");
    }
    for (int i = 0; i < result.size(); i++) {
      WSHandlerResult res = (WSHandlerResult) result.get(i);
      for (int j = 0; j < res.getResults().size(); j++) {
        WSSecurityEngineResult secRes = (WSSecurityEngineResult) res.getResults().get(j);
        int action = (Integer)secRes.get(WSSecurityEngineResult.TAG_ACTION);

        // USER TOKEN
        if ((action & WSConstants.UT) > 0) {
          WSUsernameTokenPrincipal principal = (WSUsernameTokenPrincipal) secRes.get(WSSecurityEngineResult.TAG_PRINCIPAL);
          // "Set user property to user from UT to allow response encryption"
          // (probably not needed at this point at Visana)
          message.put(WSHandlerConstants.ENCRYPTION_USER, principal.getName());

          // Set the Spring Security Context

          Authentication authentication = new UsernamePasswordAuthenticationToken(principal.getName(), principal.getPassword());
          if (isVerifyUser()) {
            authentication = getAuthenticationManager().authenticate(authentication);
          }
          SecurityContextHolder.getContext().setAuthentication(authentication);

          // This class do not clean the SecurityContextHolder. The user have to
          // insure that it will be cleaned in a filter.
        }
      }
    }
  }



  /**
   * getter for the properties property.
   * 
   * @return the properties
   */
  public Map<String, Object> getProperties()
  {
    return properties;
  }



  /**
   * setter for the properties property.
   * 
   * @param properties
   *          the properties to set
   */
  public void setProperties(Map<String, Object> properties)
  {
    this.properties = properties;
  }



  /**
   * getter for the before property.
   * 
   * @return the before
   */
  public Set<String> getBefore()
  {
    return before;
  }



  /**
   * setter for the before property.
   * 
   * @param before
   *          the before to set
   */
  public void setBefore(Set<String> before)
  {
    this.before = before;
  }



  /**
   * getter for the after property.
   * 
   * @return the after
   */
  public Set<String> getAfter()
  {
    return after;
  }



  /**
   * setter for the after property.
   * 
   * @param after
   *          the after to set
   */
  public void setAfter(Set<String> after)
  {
    this.after = after;
  }



  /**
   * getter for the phase property.
   * 
   * @return the phase
   */
  public String getPhase()
  {
    return phase;
  }



  /**
   * getter for the id property.
   * 
   * @return the id
   */
  public String getId()
  {
    return id;
  }

}
